package com.zz.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.zz.excel.anno.ImportExcelMark;
import com.zz.excel.utils.CloneUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * 导表入库监听器
 *
 * @param <T> excel映射实体类的泛型
 * @param <S> Service接口泛型
 * @desc 这套监听器模板的通用点在于：
 * 1）需要手动在ManagerImpl中使用@ImportExcelMark注解标记【落库】方法
 * 2）手动传入excel实体类泛型、Manager泛型、Manager对象
 * 3）如果excel实体类和Manager层中落库方法的参数实体类不一致，还需要进行转换（待解决）
 */
@Slf4j
public class NewEasyExcelListener<T, S> extends AnalysisEventListener<T> {

    private static final int BATCH_COUNT = 100;

    /**
     * 缓存的数据
     **/
    private List<T> cacheList = new ArrayList<>(BATCH_COUNT);

    S beanService;

    public NewEasyExcelListener(S beanService) {
        this.beanService = beanService;
    }


    @Override
    public void invoke(T data, AnalysisContext analysisContext) {
        log.info("读取到数据：{}", data);
        cacheList.add(data);
        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (cacheList.size() >= BATCH_COUNT) {
            saveData(cacheList);
            // 存储完成清理 list
            cacheList = new ArrayList<>(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        saveData(cacheList);
        log.info("所有数据解析完成！");
    }

    /**
     * 往数据库中插入数据
     *
     * @param dataList
     */
    public void saveData(List<T> dataList) {
        Method[] methods = beanService.getClass().getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(ImportExcelMark.class)) {
                log.info("方法{}已被标记", method.getName());
                try {
                    /** 情况1：存在实体类数据类型转换时 **/
                    //获得method方法参数中List的泛型
                    Class<?> dtoClazz = getListGenericClassInMethod(method);
                    //log.info("{}方法中参数List的泛型为{}", method.getName(), dtoClazz);
                    List dtoList = CloneUtils.batchClone(dataList, dtoClazz);
                    //log.info("转换后的集合结果为{}", dtoList);
                    Integer result = (Integer) method.invoke(beanService, dtoList);

                    /** 情况2：不存在实体类数据类型转换时 **/
                    //Integer result = (Integer) method.invoke(beanService, dataList);
                    if (result == 0) {
                        log.error("往表中批量插入数据失败！");
                        throw new RuntimeException("往表中批量插入数据失败！");
                    }
                    log.info("存储数据库成功！");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } finally {
                }
            }
        }
    }

    /**
     * 获得List<T>中泛型类对应的Class对象
     * @param method
     * @return
     */
    public Class<?> getListGenericClassInMethod(Method method) {
        Class<?> aClass = null;
        try {
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            ParameterizedType listType = (ParameterizedType) genericParameterTypes[0];  //java.util.List<com.zz.excel.dto.ExcelTwoDto>
            Type actualType = listType.getActualTypeArguments()[0];  //class com.zz.excel.dto.ExcelTwoDto
            String typeName = actualType.getTypeName();  //com.zz.excel.dto.ExcelTwoDto
            aClass = Class.forName(typeName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return aClass;
    }
}
